package com.societyGames.flashForms
{
	import flash.display.DisplayObject;
	import flash.display.Sprite;
	import flash.display.Stage;
	import flash.events.MouseEvent;
	import flash.ui.Mouse;

	public class MouseCursor
	{
		private static var _instance:MouseCursor;
		private var _setObject:Object;
		private var _lockObject:Object;
		private var _cursor:DisplayObject;
		private var _stage:Stage;
		private var _isCursorVisible:Boolean = true;
		private var _dx:Number;
		private var _dy:Number;

		public static function initialize(stage:Stage):void
		{
			if (_instance != null)
			{
				throw new Error("Cannot reinitialize singleton.");
			}
			_instance = new MouseCursor(stage);
		}
		
		public static function get instance():MouseCursor
		{
			return _instance;	
		}

		public function MouseCursor(stage:Stage)
		{
			if (stage == null)
			{
				throw new ArgumentError("Argument cannot be null.");
			}
			this._stage = stage;
			this._stage.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler);
			this._stage.addEventListener(MouseEvent.ROLL_OVER, rollOverHandler);
		}
		
		/**
		 * Sets the cursor as long as it is not locked.
		 * @param cursor The cursor to use.
		 * @param setObject The object to check for on a unconditional unset.
		 */		
		public function setCursor(cursor:DisplayObject, setObject:Object = null, dx:Number = 0, dy:Number = 0):void
		{
			if (this._lockObject == null)
			{	
				clearCursor();
				this._setObject = setObject;
				useCursor(cursor, dx, dy);
				Mouse.hide();
			}
		}
		
		/**
		 * Unsets the cursor.
		 * @param setObject If null, will unset any cursor. If non-null will only unset the cursor if the setObject matches.
		 */
		public function unsetCursor(setObject:Object = null):void
		{
			if (this._lockObject == null)
			{
				if (setObject == null || setObject == this._setObject)
				{
					clearCursor();
					this._setObject = null;
				}
			}
		}
		
		/**
		 * Sets the cursor, only to be unlocked by a particular object.
		 * @param cursor The cursor to use.
		 * @param lockObject The object that can unlock the cursor.
		 **/
		public function lockCursor(cursor:DisplayObject, lockObject:Object, dx:Number = 0, dy:Number = 0):void
		{
			if (this._lockObject == null || this._lockObject == lockObject)
			{	
				clearCursor();
				this._setObject = null;
				this._lockObject = lockObject;
				useCursor(cursor, dx, dy);
			}
		}
		
		public function unlockCursor(lockObject:Object):void
		{
			if (this._lockObject == lockObject)
			{	
				clearCursor();
				this._lockObject = null;
				Mouse.show();
			}
		}
		
		private function rollOverHandler(event:MouseEvent):void
		{
			this._isCursorVisible = true;
			this.synchronizeCursorVisibility();
		}
		
		private function rollOutHandler(event:MouseEvent):void
		{
			this._isCursorVisible = false;
			this.synchronizeCursorVisibility();
		}
		
		private function mouseMoveHandler(event:MouseEvent):void
		{
			this._cursor.x = int(this._stage.mouseX) + _dx;
			this._cursor.y = int(this._stage.mouseY) + _dy;
		}

		private function synchronizeCursorVisibility():void
		{
			if (this._cursor != null)
			{
				this._cursor.visible = this._isCursorVisible;
			}
		}
		
		private function useCursor(cursor:DisplayObject, dx:Number, dy:Number):void
		{
			this._cursor = cursor;
			this._dx = dx;
			this._dy = dy;
			
			if (this._cursor != null)
			{
				this._stage.addChild(this._cursor);
				this._cursor.x = int(this._stage.mouseX) + this._dx;
				this._cursor.y = int(this._stage.mouseY) + this._dy;
				synchronizeCursorVisibility();
				this._stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
			}
			Mouse.hide();
		}
		
		private function clearCursor():void			
		{
			if (this._cursor != null)
			{
				this._stage.removeChild(this._cursor);
				this._stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
				this._cursor = null;
			}
			Mouse.show();
		}
	}
}